package com.iwjw.ibatis.plugins.page;

import com.iwjw.ibatis.entity.Page;
import com.iwjw.ibatis.exception.IWMybatisException;
import com.iwjw.ibatis.plugins.PluginUtil;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;

/**
 * 在数据库选择完成后过滤
 *
 * @author: huangweiwei
 * @email: ihuangweiwei@163.com
 * @created:2016/10/31
 */
@Intercepts({
        @Signature(type = StatementHandler.class,
                method = "prepare",
                args = {Connection.class, Integer.class})})
public class PagePlugin implements Interceptor {

    protected static final Logger logger = LoggerFactory.getLogger(PagePlugin.class);

    private Integer defaultPage;
    private Integer defaultPageSize;
    private Boolean defaultUseFlag;


    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        MetaObject metaStatementHandler = PluginUtil.getMetaObject((StatementHandler) invocation.getTarget());
        String sql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
        RowBounds rowBounds = (RowBounds) metaStatementHandler.getValue("delegate.rowBounds");
            /* 不需要分页的场合 */
        if (rowBounds == null || rowBounds == RowBounds.DEFAULT) {
            return invocation.proceed();
        }

        //不是select语句.
        if (!this.checkSelect(sql)) {
            return invocation.proceed();
        }
        BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
        if (rowBounds instanceof Page) {
            Page page = (Page) rowBounds;
            //获取参数.
            Integer pageNum = page.getPage() == null ? this.defaultPage : page.getPage();
            Integer pageSize = page.getPageSize() == null ? this.defaultPageSize : page.getPageSize();
            Boolean useFlag = page.getUseFlag() == null ? this.defaultUseFlag : page.getUseFlag();
            if (!useFlag) {  //不使用分页插件.
                return invocation.proceed();
            }
            int total = this.getTotal(invocation, metaStatementHandler, boundSql);
            page.setPage(pageNum);
            page.setPageSize(pageSize);
            page.setTotal(total);
            if (total <= 0) {
                return invocation.proceed();
            }
            //计算总页数.
            int totalPage = total % pageSize == 0 ? total / pageSize : total / pageSize + 1;
            page.setAllPage(totalPage);
            //检查当前页码的有效性.
            if (!this.checkPage(pageNum, totalPage)) {
                throw new IWMybatisException();
            }
            this.changeSQL(metaStatementHandler, boundSql, pageNum, pageSize);
        }
        return invocation.proceed();
    }


    @Override
    public Object plugin(Object statementHandler) {
        return Plugin.wrap(statementHandler, this);
    }

    @Override
    public void setProperties(Properties props) {
        String strDefaultPage = props.getProperty("default.page", "1");
        String strDefaultPageSize = props.getProperty("default.pageSize", "50");
        String strDefaultUseFlag = props.getProperty("default.useFlag", "true");
        this.defaultPage = Integer.parseInt(strDefaultPage);
        this.defaultPageSize = Integer.parseInt(strDefaultPageSize);
        this.defaultUseFlag = Boolean.parseBoolean(strDefaultUseFlag);
    }

    /**
     * 判断是否sql语句.
     *
     * @param sql
     * @return
     */
    private boolean checkSelect(String sql) {
        String trimSql = sql.trim();
        int idx = trimSql.toLowerCase().indexOf("select");
        return idx == 0;
    }

    /**
     * 检测页码是否有效
     *
     * @param pageNum
     * @param pageTotal
     * @throws Throwable
     */
    private boolean checkPage(Integer pageNum, Integer pageTotal) throws Throwable {
        return pageNum <= pageTotal;
    }


    /**
     * 修改当前查询的SQL
     *
     * @param metaStatementHandler
     * @param boundSql
     * @param page
     * @param pageSize
     */
    private void changeSQL(MetaObject metaStatementHandler, BoundSql boundSql, int page, int pageSize) {
        //获取当前需要执行的SQL
        String sql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
        /**
         * TODO 这里使用的是MySQL其他数据库需要修改.
         * 根据你的数据库，修改分页的SQL
         */
        String newSql = sql + " limit " + (page - 1) * pageSize + ", " + pageSize;
        //修改当前需要执行的SQL
        logger.info("new Sql:{}", newSql);
        metaStatementHandler.setValue("delegate.boundSql.sql", newSql);
    }

    /**
     * 获取综述.
     *
     * @param ivt                  Invocation
     * @param metaStatementHandler statementHandler
     * @param boundSql             sql
     * @return sql查询总数.
     * @throws Throwable 异常.
     */
    private int getTotal(Invocation ivt, MetaObject metaStatementHandler, BoundSql boundSql) throws Throwable {

        long begin = System.currentTimeMillis();
        //获取当前的mappedStatement
        MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
        //配置对象
        Configuration cfg = mappedStatement.getConfiguration();
        //当前需要执行的SQL
        String sql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
        /**
         * TODO 如果是其他的数据库需要按你数据库的SQL规范改写.
         * 改写为统计总数的SQL
         */
        String sqlUse = sql;
        int from = sql.toUpperCase().indexOf(" FROM");
        int order_by = sql.toUpperCase().lastIndexOf("ORDER BY");


        if (order_by > -1) {
            sqlUse = sql.substring(from, order_by);
        }

        String countSql = "select count(0) as total " + sqlUse;
        //获取拦截方法参数，我们知道是Connection对象.
        Connection connection = (Connection) ivt.getArgs()[0];
        PreparedStatement ps = null;
        int total = 0;
        try {
            //预编译统计总数SQL
            ps = connection.prepareStatement(countSql);
            //构建统计总数SQL
            BoundSql newBoundSql = new BoundSql(cfg, countSql, boundSql.getParameterMappings(), boundSql.getParameterObject());
            PluginUtil.addMetaParametersAndAdditionParameters(newBoundSql, boundSql);
            //构建MyBatis的ParameterHandler用来设置总数Sql的参数。
            ParameterHandler handler = new DefaultParameterHandler(mappedStatement, boundSql.getParameterObject(), newBoundSql);
            //设置总数SQL参数
            handler.setParameters(ps);
            //执行查询.
            ResultSet rs = ps.executeQuery();
            while (rs.next()) {
                total = rs.getInt("total");
            }
        } finally {
            //这里不能关闭Connection否则后续的SQL就没法继续了。
            if (ps != null && ps.isClosed()) {
                ps.close();
            }
        }
        logger.debug("总条数：{}", total);
        logger.info("sql:{};cost:{} ms", countSql, System.currentTimeMillis() - begin);
        return total;
    }


}
